package net.ib.mtalk.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;

import net.ib.mntalk.protocol.Common.UUIDMsg;
import net.ib.mtalk.R;
import net.ib.mtalk.constant.MTalkConstant;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Environment;
import android.os.Handler;
import android.os.Handler.Callback;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.util.Log;
import android.widget.ImageView;

/**
 *  Profile 을 loading 하고 cache 하는 class 이다. </p>
 *  mTalk 에서는 Profile Image 를 여러 곳 에서 사용하며,</br>
 *   추후에 tablet 용 Dual Screen 을 염두해 Singleton 으로 구현 됨</p>
 *  
 * @author namkhoh
 *
 */
public class ProfileImageLoader implements Callback{
	
	private static ProfileImageLoader loader = null;
	
	public static ProfileImageLoader getInstance(Context mContext) {
		if (loader == null) {
			synchronized (ProfileImageLoader.class) {
				loader = new ProfileImageLoader(mContext);
			}
		}
		return loader;
	}
	
	
	private final Context mContext;
	
    /**
     * Type of message sent by the UI thread to itself to indicate that some photos
     * need to be loaded.
     */
    private static final int MESSAGE_REQUEST_LOADING = 1;

    /**
     * Type of message sent by the loader thread to indicate that some photos have
     * been loaded.
     */
    private static final int MESSAGE_PHOTOS_LOADED = 2;
	
    /**
     * Bitmap 을 SoftReference 로 보관하여, memory 가 부족할 경우 Garbage Collector 가 회수 할 수 있도록 한다. 
     * @author namkhoh
     *
     */
	private static class ProfileImageHolder {
		private static final int NEEDED = 0;
		private static final int LOADING = 1;
		private static final int LOADED = 2;
		
		int state;
		SoftReference<Bitmap> bitmapRef = null;
	}
		
	/**
	 * When Scrolling, mPaused should be set true
	 */
	private boolean mPaused = false;
	
	/**
	 * 기본 Image
	 */
	private final int DEFAULT_IMAGE	=	R.drawable.pic_default_small;
	
	private static final int SMALL_PROFILE_SIZE	=	58;
	
	private static final String className = "ProfileDownloader";
	
    private boolean mLoadingRequested;
	
    private LoaderThread mLoaderThread;
    
	private final ConcurrentHashMap<String, ProfileImageHolder> mBitmapCache =
			new ConcurrentHashMap<String, ProfileImageLoader.ProfileImageHolder>();
	
	private final ConcurrentHashMap<ImageView, String> mPendingRequest = 
			new ConcurrentHashMap<ImageView, String>();
		
	private final Handler mMainThreadHandler = new Handler(Looper.getMainLooper(), this);
	
	private ProfileImageLoader(Context context) {
		this.mContext = context;
	}
	
	private void requestLoading() {
        if (!mLoadingRequested) {
            mLoadingRequested = true;
            mMainThreadHandler.sendEmptyMessage(MESSAGE_REQUEST_LOADING);
        }
    }

	@Override
	public boolean handleMessage(Message msg) {
		switch (msg.what) {
		case MESSAGE_REQUEST_LOADING:
			mLoadingRequested = false;
			if (!mPaused) {
				if (mLoaderThread == null) {
					mLoaderThread = new LoaderThread(mContext);
					mLoaderThread.start();
				}
				mLoaderThread.requestLoading();
			}
			return true;
		case MESSAGE_PHOTOS_LOADED:
			if (!mPaused) {
				processLoadedImages();
			}
			return true;
		}
		return false;
	}
	
	public void deleteCache(UUIDMsg buddyUuid){
		mBitmapCache.remove(GpbSupport.UuidMsgToString(buddyUuid));
	}
	
	/**
	 * Profile Image 가 삭제 변경된 경우에 대해서 해당 Image 를 삭제 한 뒤, memory 에서 제거 한다. 
	 * @param buddyUuid
	 */
	public void deleteImage(UUIDMsg buddyUuid) {
		mBitmapCache.remove(GpbSupport.UuidMsgToString(buddyUuid));
		File mFile = new File(mContext.getFilesDir(), GpbSupport.UuidMsgToString(buddyUuid));
		if (mFile.exists()) {
			mFile.delete();
		}
		File sdCardFile = new File(MTalkConstant.MTALK_PATH + "/buddy");
		if (sdCardFile.exists()) {
			File imageFile = new File(sdCardFile, GpbSupport.UuidMsgToString(buddyUuid));
			if (imageFile.exists()) {
				Log.i("ZOO", "파일삭제");
				imageFile.delete();
			}
		}
	}

    /**
     * Singleton 으로 Loader 를 구현했기 때문에 pause(), resume() 사용 불가
     */
	@Deprecated
	public void pause() {
		mPaused = true;
	}
	
    /**
     * Singleton 으로 Loader 를 구현했기 때문에 pause(), resume() 사용 불가
     */
	@Deprecated
	public void resume() {
		mPaused = false;
		if (!mPendingRequest.isEmpty()) {
			requestLoading();
		}
	}
	
	public void loadProfileImage(ImageView view, String uuid) {
		boolean loaded = loadCachedPhoto(view, uuid);
		if (loaded) {
			mPendingRequest.remove(view);
		} else {
			mPendingRequest.put(view, uuid);
			if (!mPaused) {
				requestLoading();
			}
		}
	}
	
	private void processLoadedImages() {
		Iterator<ImageView> iterator = mPendingRequest.keySet().iterator();
		while(iterator.hasNext()) {
			ImageView view = iterator.next();
			String buddyId = mPendingRequest.get(view);
			boolean loaded = loadCachedPhoto(view, buddyId);
			if (loaded) {
				iterator.remove();
			}
		}
		if (!mPendingRequest.isEmpty()) {
			requestLoading();
		}
	}
	
	/**
	 * Profile 을  불러온다. 
	 * @param buddyNO
	 * @return
	 */
	Bitmap download(String buddyNO) {
		return BitmapFactory.decodeFile(
				MTalkConstant.MTALK_PATH + "/buddy/" + buddyNO);
	}
	
    /**
     * Cache 에 Profile Image 가 있는지 검사한다. 만약 존재한다면, 넘겨받은 ImageView 에 바로 Set 해준다. 
     * 존재하지 않는 경우에는 현재 상태를, {@link ProfileImageHolder#NEEDED} 로 설정한다.
     * 이 경우에, Loading 이 진행될 동안은 default Image 가 setting 된다. 
     * @param view
     * @param buddyNO
     * @return
     */
	private boolean loadCachedPhoto(ImageView view, String uuid) {
		ProfileImageHolder holder = mBitmapCache.get(uuid);
		if (holder == null) {
			holder = new ProfileImageHolder();
			mBitmapCache.put(uuid, holder);
		} else if (holder.state == ProfileImageHolder.LOADED){
			// Null bitmap reference means that no image
			if (holder.bitmapRef == null) {
				view.setImageResource(DEFAULT_IMAGE);
				return true;
			}
			
			Bitmap bitmap = holder.bitmapRef.get();
			if (bitmap != null) {
				view.setImageBitmap(bitmap);
				return true;
			}
			
            // Null bitmap means that the soft reference was released by the GC
            // and we need to reload the photo.
            holder.bitmapRef = null;
		}
		
		// The bitmap has not been loaded, 
		view.setImageResource(DEFAULT_IMAGE);
		holder.state = ProfileImageHolder.NEEDED;
		return false;
	}
	
	/**
	 * Handler Thread 는 그 자체로 내부에 Message Queue 를 지니기 때문에 내부적으로 Queuing 을 해준다. 
	 * @author namkhoh
	 *
	 */
	private class LoaderThread extends HandlerThread implements Callback {
		private final Context mContext;
		private Handler mLoaderThreadHandler;
		private final ArrayList<String> mBuddyNO = CollectionsUtil.newArrayList();
		private final ArrayList<String> mLoopBuddyNO = CollectionsUtil.newArrayList();
		public LoaderThread(Context context) {
			super(className);
			this.mContext = context;
		}
		
		public void requestLoading() {
			if (mLoaderThreadHandler == null) {
				mLoaderThreadHandler = new Handler(getLooper(), this);
			}
			mLoaderThreadHandler.sendEmptyMessage(0);
		}
		
		@Override
		public boolean handleMessage(Message msg) {
			loadProfileImage();
			mMainThreadHandler.sendEmptyMessage(MESSAGE_PHOTOS_LOADED);
			return true;
		}
		
		/**
		 * 실제로 File 에서 Image search 가 이뤄지는 부분
		 */
		private void loadProfileImage() {
			obtainBuddyNOToLoad(mBuddyNO, mLoopBuddyNO);
			
			Bitmap mBitmap;
			for (String buddyNO : mLoopBuddyNO) {
				mBitmap = null;
				// 1. Search in File system
				mBitmap = loadFromFile(mContext, buddyNO);
				if (mBitmap == null) {
					//2. set image from file
					mBitmap = download(buddyNO);
					if (mBitmap != null) {
						MTalkLogWriter.v("Profile Image download Success, BuddyNO : " + buddyNO + " " + getClass().getSimpleName());
//						saveBitmapToFile(mBitmap, buddyNO);
					}
				}
				addBitmapToCache(buddyNO, mBitmap);
				mBuddyNO.remove(buddyNO);
			}
		}
	}

	
	/**
	 * Loading 이 필요한 BuddyNO 를 만들어 낸다. 
	 * {@link ConcurrentHashMap} 을 사용하기 때문에 현재 Load 가 필요한 buddy 를 따로 ArrayList 에 담는다.
	 * 이는 우리가 현재 data 를 추출하는 중에도 새로운 buddy 가 PendingRequest 에 추가될 수 있기 때문이다. 
	 * @param buddyNO
	 * @param loadingBuddies
	 */
	private void obtainBuddyNOToLoad(ArrayList<String> buddyNO, ArrayList<String> loopBuddyNO) {
		buddyNO.clear();
		loopBuddyNO.clear();
		
		Iterator<String> iterator = mPendingRequest.values().iterator();
		while (iterator.hasNext()) {
			String mBuddyNO = iterator.next();
			ProfileImageHolder holder =  mBitmapCache.get(mBuddyNO);
			if (holder != null && holder.state == ProfileImageHolder.NEEDED) {
				holder.state = ProfileImageHolder.LOADING;
				buddyNO.add(mBuddyNO);
				loopBuddyNO.add(mBuddyNO);
			}
		}
	}
	
	

	/**
	 * Application File System 에 image 가 존재하면 해당 File 을 로딩한다.
	 * 존재하지 않는 다면, null 을 return 
	 * @param buddyNO
	 * @return
	 */
	private Bitmap loadFromFile(Context mContext, String buddyNO) {
		String fileName = buddyNO;
		File mFile = new File(mContext.getFilesDir(), fileName);
		if (!mFile.exists()) return null;
		FileInputStream fis = null;
		try {
			fis = mContext.openFileInput(fileName);
			return BitmapFactory.decodeStream(fis);
		} catch (Exception e) {
			MTalkLogWriter.e(e + getClass().getSimpleName());
		} finally {
			try {
				if (fis!=null)fis.close();
			} catch (IOException e) {
				MTalkLogWriter.e(e + getClass().getSimpleName());
			}
		}
		return null;
	}

	/**
	 * Server 로 부터 받은 Bitmap 을 저장한다.
	 * @param bitmap
	 * @param buddyNO
	 */
//	private void saveBitmapToFile(Bitmap bitmap, String buddyNO) {
//		final String fileName = buddyNO+".jpeg_";
//		FileOutputStream fos = null;
//		try {
//			fos = mContext.openFileOutput(fileName, Context.MODE_PRIVATE);
//			bitmap.compress(CompressFormat.JPEG, 90, fos);
//			
//		} catch (FileNotFoundException e) {
//			MTalkLogWriter.e(e + getClass().getSimpleName());
//		} finally {
//			try {
//				if (fos != null) fos.close();
//			} catch (IOException e) {
//				MTalkLogWriter.e(e + getClass().getSimpleName());
//			}
//		}
//	}
	
	/**
	 * Bitmap 을 Cache 에 보관한다. 
	 * 해당 Buddy 에 대해서 Profile Image 가 없는 경우에는 bitmap 에 자동으로 null 이 Set
	 * @param buddyNO
	 * @param bitmap
	 */
	private void addBitmapToCache(String buddyNO, Bitmap bitmap) {
		if (mPaused) {
			return;
		}
		ProfileImageHolder holder = new ProfileImageHolder();
		holder.state = ProfileImageHolder.LOADED;
		if (bitmap != null) {
			holder.bitmapRef = new SoftReference<Bitmap>(bitmap);
		}
		mBitmapCache.put(buddyNO, holder);
	}
	
	/**
	 * Size 와 BuddyNO 에 맞춰서 
	 * @param buddyNO
	 * @param size
	 * @return
	 */
//	public static String getProfileImageUrl(String buddyNO, int size) {
//		String url = null;
//		url = ServerAddressMgr.getContactServer(ServerType.PRIMARY)
//						+NetworkConstant.CONTACT_BUDDY
//						+"/"+buddyNO
//						+NetworkConstant.CONTACT_BUDDY_IMAGE
//						+"/?"+NetworkConstant.KEY_UID+"="
//						+ChatONPref.getInstance().getString(PrefConst.PREF_UID, "")
//						+"&"
//						+NetworkConstant.KEY_IMEI+"="
//						+DeviceInfoUtil.getIMEI()
//						+"&"
//						+NetworkConstant.KEY_SIZE+"="
//						+size;
//		return url;
//	}
}
